{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orgd176733\"></a>\n",
    "# Python Web开发初识\n",
    "\n",
    "\n",
    "<a id=\"orgb777448\"></a>\n",
    "\n",
    "## HTTP client-server\n",
    "\n",
    "![img](../images/http_client-server.png \"HTTP Client-Server\")\n",
    "\n",
    "-   通过请求和响应的交换达成通信\n",
    "-   不保存通信状态（stateless）\n",
    "-   使用 **URI** 定位互联网上的资源\n",
    "-   请求资源时使用方法下达命令（GET、POST、HEAD等）\n",
    "-   通过持久连接节省通信量\n",
    "-   使用cookie来进行状态管理\n",
    "\n",
    "\n",
    "<a id=\"org21ca446\"></a>\n",
    "\n",
    "### HTTP请求\n",
    "\n",
    "    GET / HTTP/1.1\n",
    "    Connection: close\n",
    "    Host: httpbin.org\n",
    "    User-agent: HTTPie/0.9.9\n",
    "    Accept: */*\n",
    "    Accept-Encoding: gzip, deflate\n",
    "    Accept-Language: en\n",
    "    Accept-Charset: *, utf-8\n",
    "\n",
    "    Optional data\n",
    "    ...\n",
    "\n",
    "-   第一行定义 **请求类型** 、 **文档（选择符）** 和 **协议版本**\n",
    "-   接着是报头行，包括各种有关客户端的信息\n",
    "-   报头行后面是一个空白行，表示报头行结束\n",
    "-   之后是发送表单的信息或者上传文件的事件中可能出现的数据\n",
    "-   报头的每一行都应该使用回车符或者换行符（'\\r\\n'）终止\n",
    "\n",
    "下表是常见HTTP请求方法：\n",
    "\n",
    "| 方法 | 描述      |\n",
    "|------|-----------|\n",
    "| GET  | 获取文档  |\n",
    "| POST | 将数据发布到表单 |\n",
    "| HEAD | 仅返回报头信息 |\n",
    "| PUT  | 将数据上传到服务器 |\n",
    "| …    | …         |\n",
    "\n",
    "\n",
    "<a id=\"org96b8141\"></a>\n",
    "\n",
    "### HTTP响应\n",
    "\n",
    "    HTTP/1.1 200 OK\n",
    "    Connection: keep-alive\n",
    "    Content-Length: 580\n",
    "    Content-Type: application/json\n",
    "    Date: Tue, 25 Apr 2017 04:28:37 GMT\n",
    "    Server: gunicorn/19.7.1\n",
    "    ...\n",
    "    Header: data\n",
    "\n",
    "    Data\n",
    "    ...\n",
    "\n",
    "-   第一行表示 **HTTP协议版本** 、 **成功代码** 和 **返回消息**\n",
    "-   响应行之后是一系列报头字段， 包含返回文档的类型、文档大小、Web服务器软件、cookie等方面的信息\n",
    "-   通过空白行结束报头\n",
    "-   之后是所请求文档的原始数据\n",
    "\n",
    "下表是HTTP常见状态码：\n",
    "\n",
    "| 代码       | 描述    | 符号常量                |\n",
    "|------------|---------|-------------------------|\n",
    "| 成功代码（2xx） |         |                         |\n",
    "| 200        | 成功    | OK                      |\n",
    "| 201        | 创建    | CREATED                 |\n",
    "| 202        | 接受    | ACCEPTED                |\n",
    "| 204        | 无内容  | NO\\_CONTENT             |\n",
    "| 重定向（3xx） |         |                         |\n",
    "| 300        | 多种选择 | MULTIPLE\\_CHOICES       |\n",
    "| 301        | 永久移动 | MOVED\\_PERMANENTLY      |\n",
    "| 302        | 可被303替代 | FOUND                   |\n",
    "| 303        | 临时移动 | SEE\\_OTHER              |\n",
    "| 304        | 不修改  | NOT\\_MODIFIED           |\n",
    "| 客户端错误（4xx） |         |                         |\n",
    "| 400        | 请求错误 | BAD\\_REQUEST            |\n",
    "| 401        | 未授权  | UNAUTHORIZED            |\n",
    "| 403        | 禁止访问 | FORBIDDEN               |\n",
    "| 404        | 未找到  | NOT\\_FOUND              |\n",
    "| 405        | 方法不允许 | METHOD\\_NOT\\_ALLOWED    |\n",
    "| 服务器错误（5xx） |         |                         |\n",
    "| 500        | 内部服务器错误 | INTERNAL\\_SERVER\\_ERROR |\n",
    "| 501        | 未实现  | NOT\\_IMPLEMENTED        |\n",
    "| 502        | 网关错误 | BAD\\_GATEWAY            |\n",
    "| 503        | 服务不可用 | SERVICE\\_UNAVAILABLE    |\n",
    "\n",
    "\n",
    "<a id=\"org918498b\"></a>\n",
    "\n",
    "## Python3 标准Web库\n",
    "\n",
    "-   <code>http</code>处理所有客户端—服务器HTTP请求的具体细节\n",
    "    -   <code>client</code>处理客户端部分\n",
    "    -   <code>server</code>提供了实现HTTP服务器的各种类\n",
    "    -   <code>cookies</code>支持在服务器端处理HTTP cookie\n",
    "    -   <code>cookiejar</code>支持在客户端存储和管理HTTP cookie\n",
    "\n",
    "-   <code>urllib</code>基于<code>http</code>的高层库，用于编写与HTTP服务器等交互的客户端\n",
    "    -   <code>request</code>处理客户端请求\n",
    "    -   <code>response</code>处理服务器端响应\n",
    "    -   <code>parse</code>用于操作URL字符串\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "import urllib.request as ur\n",
    "url = 'http://httpbin.org/'\n",
    "conn = ur.urlopen(url)\n",
    "print(conn)\n",
    "print('=' * 50)\n",
    "data = conn.read()\n",
    "print(data[:16])\n",
    "print(conn.status)\n",
    "\n",
    "print(conn.getheader('Content-Type'))\n",
    "for key, value in conn.getheaders():\n",
    "    print(key, value, sep=': ')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "    <http.client.HTTPResponse object at 0x10e0f1198>\n",
    "    ==================================================\n",
    "    b'<!DOCTYPE html>\\n'\n",
    "    200\n",
    "    text/html; charset=utf-8\n",
    "    Connection: close\n",
    "    Server: meinheld/0.6.1\n",
    "    Date: Fri, 14 Jul 2017 07:54:28 GMT\n",
    "    Content-Type: text/html; charset=utf-8\n",
    "    Content-Length: 12793\n",
    "    Access-Control-Allow-Origin: *\n",
    "    Access-Control-Allow-Credentials: true\n",
    "    X-Powered-By: Flask\n",
    "    X-Processed-Time: 0.00643992424011\n",
    "    Via: 1.1 vegur\n",
    "**最简单的Python Web服务器**\n",
    "\n",
    "```sh\n",
    "python -m http.server\n",
    "```\n",
    "\n",
    "    Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...\n",
    "    127.0.0.1 - - [26/Apr/2017 09:50:56] \"GET / HTTP/1.1\" 200 -\n",
    "    ...\n",
    "\n",
    "\n",
    "<a id=\"org69d8474\"></a>\n",
    "\n",
    "## 第三方工具和库\n",
    "\n",
    "\n",
    "<a id=\"org414bcbb\"></a>\n",
    "\n",
    "### HTTPie\n",
    "\n",
    "<https://httpie.org>\n",
    "\n",
    "HTTPie（读aych-tee-tee-pie）是一个 HTTP 的命令行客户端。 其目标是让 CLI 和 web 服务之间的交互尽可能的人性化。\n",
    "\n",
    "这个工具提供了简洁的<code>http</code>命令，允许通过自然的语法发送任意 HTTP 请求数据， 展示色彩化的输出。\n",
    "\n",
    "HTTPie 可用于与 HTTP 服务器做测试、调试和常规交互。\n",
    "\n",
    "HTTPie 用 Python 编写，用到了 Requests 和 Pygments 这些出色的库。\n",
    "\n",
    "\n",
    "<a id=\"org46b67cf\"></a>\n",
    "\n",
    "### httpbin\n",
    "\n",
    "<http://httpbin.org>\n",
    "\n",
    "使用 Python + Flask 编写的 HTTP 请求和响应服务。\n",
    "\n",
    "**Installation**\n",
    "\n",
    "Run it as a WSGI app using Gunicorn:\n",
    "\n",
    "```sh\n",
    "$ pip install httpbin\n",
    "$ gunicorn httpbin:app\n",
    "```\n",
    "\n",
    "    $ http http://httpbin.org/user-agent\n",
    "    HTTP/1.1 200 OK\n",
    "    Access-Control-Allow-Credentials: true\n",
    "    Access-Control-Allow-Origin: *\n",
    "    Connection: keep-alive\n",
    "    Content-Length: 35\n",
    "    Content-Type: application/json\n",
    "    Date: Wed, 26 Apr 2017 04:32:28 GMT\n",
    "    Server: gunicorn/19.7.1\n",
    "    Via: 1.1 vegur\n",
    "\n",
    "    {\n",
    "        \"user-agent\": \"HTTPie/0.9.9\"\n",
    "    }\n",
    "\n",
    "\n",
    "<a id=\"orgb611c3b\"></a>\n",
    "\n",
    "### Requests库\n",
    "\n",
    "> **HTTP for Humans.**\n",
    "\n",
    "Docs: <http://docs.python-requests.org/en/master/>\n",
    "\n",
    "Repo: <https://github.com/kennethreitz/requests>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "```python\n",
    ">>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))\n",
    ">>> r.status_code\n",
    "200\n",
    ">>> r.headers['content-type']\n",
    "'application/json; charset=utf8'\n",
    ">>> r.encoding\n",
    "'utf-8'\n",
    ">>> r.text\n",
    "u'{\"type\":\"User\"...'\n",
    ">>> r.json()\n",
    "{u'private_gists': 419, u'total_private_repos': 77, ...}\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "autoscroll": false,
    "collapsed": false,
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "import requests\n",
    "s = requests.Session()\n",
    "print(s.get('http://httpbin.org/ip').text)\n",
    "\n",
    "print(s.get('http://httpbin.org/get').json())\n",
    "\n",
    "print(s.post('http://httpbin.org/post',\n",
    "             {'key':'value'},\n",
    "             headers={'user-agent':'httpie'}).text)\n",
    "\n",
    "print(s.get('http://httpbin.org/status/404').status_code)\n",
    "\n",
    "print(s.get('http://httpbin.org/html').text)\n",
    "\n",
    "print(s.get('http://httpbin.org/deny').text)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "ein.tags": "worksheet-0",
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "<a id=\"orge48b6ca\"></a>\n",
    "\n",
    "## 简单Python Web架构\n",
    "\n",
    "![img](../images/python-web-simple-architecture.png)\n",
    "\n",
    "实际生产中，Python程序是放在服务器的 HTTP Server（比如 Apache，Nginx 等）上的。\n",
    "\n",
    "**服务器程序怎么把接受到的请求传递给Python？**\n",
    "\n",
    "**怎么在网络的数据流和Python的结构体之间转换？**\n",
    "\n",
    "处理上面两项工作就是图中 WSGI Server 做的事情。\n",
    "\n",
    "\n",
    "<a id=\"org625c6b8\"></a>\n",
    "\n",
    "### Python WSGI\n",
    "\n",
    "WSGI（Web Server Gateway Interface）是一套关于程序端和服务器端的 **规范** ，或者说统一的接口。\n",
    "\n",
    "先看一下面向 HTTP 的 Python Web程序需要关心的内容：\n",
    "\n",
    "-   请求\n",
    "    -   **请求的方法 method**\n",
    "    -   **请求的地址 url**\n",
    "    -   **请求的内容**\n",
    "    -   请求的头部 header\n",
    "    -   请求的环境信息\n",
    "\n",
    "-   响应\n",
    "    -   **状态码 status\\_code**\n",
    "    -   **响应的数据**\n",
    "    -   响应的头部\n",
    "\n",
    "WSGI的任务就是 **把上面的数据在 HTTP Server 和 Python 程序之间简单友好地传递。** 它是一个标准，被定义在[PEP 3333](https://www.python.org/dev/peps/pep-3333/#original-rationale-and-goals-from-pep-333)。需要 HTTP Server 和 Python 程序都要遵守一定的规范，实现这个标准的约定内容，才能正常工作。\n",
    "\n",
    "\n",
    "<a id=\"orgeb632b8\"></a>\n",
    "\n",
    "## Web框架有什么\n",
    "\n",
    "一个Web框架，至少要具备处理客户端请求和服务端响应的能力。\n",
    "\n",
    "-   **路由:** 解析URL并找到对应的服务端文件或者Python服务器代码。\n",
    "-   **模板:** 把服务端数据合并成HTML页面。\n",
    "-   **认证和授权:** 处理用户名、密码和权限。\n",
    "-   **Session:** 处理用户在多次请求之间需要存储的数据。\n",
    "\n",
    "\n",
    "<a id=\"orgf283d3f\"></a>\n",
    "\n",
    "## 开发环境及工具\n",
    "\n",
    "\n",
    "<a id=\"orgc670b37\"></a>\n",
    "\n",
    "### 开发环境\n",
    "\n",
    "-   **Ubuntu 16.04 LTS**\n",
    "-   LinuxBrew(Optional)\n",
    "-   **Python3.5+**\n",
    "-   **virtualenvwrapper**\n",
    "    -   create a vritualenv\n",
    "\n",
    "\n",
    "<a id=\"orga80cdce\"></a>\n",
    "\n",
    "### [Visual Studio Code](https://code.visualstudio.com)\n",
    "\n",
    "-   拓展：[Python](https://marketplace.visualstudio.com/items?itemName=donjayamanne.python), [MagicPython](https://marketplace.visualstudio.com/items?itemName=magicstack.MagicPython)\n",
    "-   用户配置文件\n",
    "\n",
    "    ```js\n",
    "    // python\n",
    "    \"python.pythonPath\": \"/usr/local/bin/python3\",\n",
    "    \"python.venvPath\": \"~/.virtualenvs\",\n",
    "    \"python.jediPath\": \"ENV_PATH/lib/python3.6/site-packages\",\n",
    "    \"python.linting.flake8Enabled\": true,\n",
    "    \"python.linting.pylintEnabled\": false,\n",
    "    \"python.formatting.provider\": \"yapf\",\n",
    "    \"python.autoComplete.addBrackets\": false,\n",
    "    \"python.autoComplete.extraPaths\": [\n",
    "        \"ENV1_PATH/lib/python3.6/site-packages\",\n",
    "        \"ENV2_PATH/lib/python3.6/site-packages\",\n",
    "        \"ENV3_PATH/lib/python3.4/site-packages\"\n",
    "    ]\n",
    "    ```"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  },
  "name": "1-python-web-dev-intro.ipynb",
  "toc": {
   "colors": {
    "hover_highlight": "#DAA520",
    "running_highlight": "#FF0000",
    "selected_highlight": "#FFD700"
   },
   "moveMenuLeft": true,
   "nav_menu": {
    "height": "282px",
    "width": "252px"
   },
   "navigate_menu": true,
   "number_sections": false,
   "sideBar": true,
   "threshold": 4,
   "toc_cell": false,
   "toc_position": {
    "height": "531px",
    "left": "0px",
    "right": "1020px",
    "top": "106px",
    "width": "242px"
   },
   "toc_section_display": "block",
   "toc_window_display": false,
   "widenNotebook": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
